/**
 *
 */
package com.browseengine.bobo.facets.impl;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;

import com.browseengine.bobo.api.BoboSegmentReader;
import com.browseengine.bobo.api.BrowseSelection;
import com.browseengine.bobo.api.FacetSpec;
import com.browseengine.bobo.facets.FacetCountCollector;
import com.browseengine.bobo.facets.FacetCountCollectorSource;
import com.browseengine.bobo.facets.FacetHandler;
import com.browseengine.bobo.facets.data.FacetDataCache;
import com.browseengine.bobo.facets.data.TermValueList;
import com.browseengine.bobo.facets.filter.GeoFacetFilter;
import com.browseengine.bobo.facets.filter.RandomAccessFilter;
import com.browseengine.bobo.facets.impl.GeoFacetCountCollector.GeoRange;
import com.browseengine.bobo.sort.DocComparatorSource;
import com.browseengine.bobo.util.BigFloatArray;
import com.browseengine.bobo.util.BigSegmentedArray;
import com.browseengine.bobo.util.GeoMatchUtil;

/** Constructor for GeoFacetHandler
 * @param name - name of the Geo facet
 *
 */
public class GeoFacetHandler extends FacetHandler<GeoFacetHandler.GeoFacetData> {

  private final String _latFieldName;
  private final String _lonFieldName;
  // variable to specify if the geo distance calculations are in miles. Default is miles
  private boolean _miles;

  public GeoFacetHandler(String name, String latFieldName, String lonFieldName) {
    super(name, new HashSet<String>(Arrays.asList(new String[] { latFieldName, lonFieldName })));
    _latFieldName = latFieldName;
    _lonFieldName = lonFieldName;
    _miles = true;
  }

  /**
   * Constructor for GeoFacetHandler
   * @param name         name of the geo facet
   * @param latFieldName name of the index field that stores the latitude value
   * @param lonFieldName name of the index field that stores the longitude value
   * @param miles        variable to specify if the geo distance calculations are in miles. False indicates distance calculation is in kilometers
   */
  public GeoFacetHandler(String name, String latFieldName, String lonFieldName, boolean miles) {
    this(name, latFieldName, lonFieldName);
    _miles = miles;
  }

  public static class GeoFacetData {
    private BigFloatArray _xValArray;
    private BigFloatArray _yValArray;
    private BigFloatArray _zValArray;

    public GeoFacetData() {
      _xValArray = null;
      _yValArray = null;
      _zValArray = null;
    }

    public GeoFacetData(BigFloatArray xvals, BigFloatArray yvals, BigFloatArray zvals) {
      _xValArray = xvals;
      _yValArray = yvals;
      _zValArray = zvals;
    }

    public static BigFloatArray newInstance(int maxDoc) {
      BigFloatArray array = new BigFloatArray(maxDoc);
      array.ensureCapacity(maxDoc);
      return array;
    }

    /**
     * @return the _xValArray
     */
    public BigFloatArray get_xValArray() {
      return _xValArray;
    }

    /**
     * @param xValArray the _xValArray to set
     */
    public void set_xValArray(BigFloatArray xValArray) {
      _xValArray = xValArray;
    }

    /**
     * @return the _yValArray
     */
    public BigFloatArray get_yValArray() {
      return _yValArray;
    }

    /**
     * @param yValArray the _yValArray to set
     */
    public void set_yValArray(BigFloatArray yValArray) {
      _yValArray = yValArray;
    }

    /**
     * @return the _zValArray
     */
    public BigFloatArray get_zValArray() {
      return _zValArray;
    }

    /**
     * @param zValArray the _zValArray to set
     */
    public void set_zValArray(BigFloatArray zValArray) {
      _zValArray = zValArray;
    }

    public void load(String latFieldName, String lonFieldName, BoboSegmentReader reader)
        throws IOException {
      if (reader == null) throw new IOException("reader object is null");

      FacetDataCache<?> latCache = (FacetDataCache<?>) reader.getFacetData(latFieldName);
      FacetDataCache<?> lonCache = (FacetDataCache<?>) reader.getFacetData(lonFieldName);

      int maxDoc = reader.maxDoc();

      BigFloatArray xVals = this._xValArray;
      BigFloatArray yVals = this._yValArray;
      BigFloatArray zVals = this._zValArray;

      if (xVals == null) xVals = newInstance(maxDoc);
      else xVals.ensureCapacity(maxDoc);
      if (yVals == null) yVals = newInstance(maxDoc);
      else yVals.ensureCapacity(maxDoc);
      if (zVals == null) zVals = newInstance(maxDoc);
      else zVals.ensureCapacity(maxDoc);

      this._xValArray = xVals;
      this._yValArray = yVals;
      this._zValArray = zVals;

      BigSegmentedArray latOrderArray = latCache.orderArray;
      TermValueList<?> latValList = latCache.valArray;

      BigSegmentedArray lonOrderArray = lonCache.orderArray;
      TermValueList<?> lonValList = lonCache.valArray;

      for (int i = 0; i < maxDoc; ++i) {
        String docLatString = latValList.get(latOrderArray.get(i)).trim();
        String docLonString = lonValList.get(lonOrderArray.get(i)).trim();

        float docLat = 0;
        if (docLatString.length() > 0) {
          docLat = Float.parseFloat(docLatString);
        }

        float docLon = 0;
        if (docLonString.length() > 0) {
          docLon = Float.parseFloat(docLonString);
        }

        float[] coords = GeoMatchUtil.geoMatchCoordsFromDegrees(docLat, docLon);
        _xValArray.add(i, coords[0]);
        _yValArray.add(i, coords[1]);
        _zValArray.add(i, coords[2]);
      }
    }
  }

  /**
   * Builds a random access filter.
   * @param value Should be of the form: lat, lon: rad
   * @param selectionProperty
   */
  @Override
  public RandomAccessFilter buildRandomAccessFilter(String value, Properties selectionProperty)
      throws IOException {
    GeoRange range = GeoFacetCountCollector.parse(value);
    return new GeoFacetFilter(this, range.getLat(), range.getLon(), range.getRad(), _miles);
  }

  @Override
  public DocComparatorSource getDocComparatorSource() {
    throw new UnsupportedOperationException("Doc comparator not yet supported for Geo Facets");
  }

  @Override
  public FacetCountCollectorSource getFacetCountCollectorSource(final BrowseSelection sel,
      final FacetSpec fspec) {
    return new FacetCountCollectorSource() {
      final List<String> ranges = Arrays.asList(sel.getValues());

      @Override
      public FacetCountCollector getFacetCountCollector(BoboSegmentReader reader, int docBase) {
        GeoFacetData dataCache = getFacetData(reader);
        return new GeoFacetCountCollector(_name, dataCache, docBase, fspec, ranges, _miles);
      }
    };
  }

  @Override
  public String[] getFieldValues(BoboSegmentReader reader, int id) {
    GeoFacetData dataCache = getFacetData(reader);
    BigFloatArray xvals = dataCache.get_xValArray();
    BigFloatArray yvals = dataCache.get_yValArray();
    BigFloatArray zvals = dataCache.get_zValArray();

    float xvalue = xvals.get(id);
    float yvalue = yvals.get(id);
    float zvalue = zvals.get(id);
    float lat = GeoMatchUtil.getMatchLatDegreesFromXYZCoords(xvalue, yvalue, zvalue);
    float lon = GeoMatchUtil.getMatchLonDegreesFromXYZCoords(xvalue, yvalue, zvalue);

    String[] fieldValues = new String[2];
    fieldValues[0] = String.valueOf(lat);
    fieldValues[1] = String.valueOf(lon);
    return fieldValues;
  }

  @Override
  public GeoFacetData load(BoboSegmentReader reader) throws IOException {
    GeoFacetData dataCache = new GeoFacetData();
    dataCache.load(_latFieldName, _lonFieldName, reader);
    return dataCache;
  }

}
